///\todo upgrade

//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_IMAGE_ALGORITHM_TRANSFORM_H_
#define _BUOLA_IMAGE_ALGORITHM_TRANSFORM_H_

#include <buola/image/algorithm/detail/opencv.h>

namespace buola { namespace img {

//! \name sampling, interpolation and geometrical transforms
//@{

struct CMap
{
    CMap()
    {
    }
    
    CMap(const CSize_i &pSize)
        :   mX(pSize),
            mY(pSize)
    {
    }
    
    void Recreate(const CSize_i &pSize)
    {
        mX.Recreate(pSize);
        mY.Recreate(pSize);
    }
    
    int Width() const       {   return mX.Width();  }
    int Height() const      {   return mX.Height(); }
    const CSize_i &Size() const    {   return mX.Size();   }

    img::CImage_gray32f mX,mY;
};

struct CRectifyMap
{
    CRectifyMap()
    {
    }
    
    CRectifyMap(const CSize_i &pSize)
        :   mLeft(pSize),
            mRight(pSize)
    {
    }
    
    void Recreate(const CSize_i &pSize)
    {
        mLeft.Recreate(pSize);
        mRight.Recreate(pSize);
    }
    
    int Width() const       {   return mLeft.Width();   }
    int Height() const      {   return mLeft.Height();  }
    const CSize_i &Size() const    {   return mLeft.Size();   }

    CMap mLeft,mRight;

    CPoint_d mCenter;
};
    
template<typename tFormat>
inline void resize(const CView<tFormat> &pSrc,const CMView<tFormat> &pDst,int pInterp=CV_INTER_LINEAR)
{
    cv::resize(mat_wrap(pSrc),mat_wrap(pDst),cv::Size(pDst.Width(),pDst.Height()),0,0,pInterp);
}

template<typename tFormat>
inline void resize(CImage<tFormat> &pImg,const CSize_i &pSize,int pInterp=CV_INTER_LINEAR)
{
    CImage<tFormat> lImg(pSize);
    resize(pImg,lImg,pInterp);
    pImg=std::move(lImg);
}

template<typename tFormat>
inline void remap(const CView<tFormat> &pSrc,const CMView<tFormat> &pDst,const CView_gray32f &pXMap,
                  const CView_gray32f &pYMap,int pInterp=cv::INTER_LINEAR)
{
    cv::remap(mat_wrap(pSrc),mat_wrap(pDst),mat_wrap(pXMap),mat_wrap(pYMap),pInterp);
}

template<typename tFormat>
inline void remap(const CView<tFormat> &pSrc,const CMView<tFormat> &pDst,CMap &pMap,int pInterp=cv::INTER_LINEAR)
{
    cv::remap(mat_wrap(pSrc),mat_wrap(pDst),mat_wrap(pMap.mX),mat_wrap(pMap.mY),pInterp);
}

template<typename tFormat>
inline void pyr_up(const CView<tFormat> &pSrc,const CMView<tFormat> &pDst)
{
    cv::pyrUp(mat_wrap(pSrc),mat_wrap(pDst),cv::Size(pDst.Width(),pDst.Height()));
}

template<typename tFormat>
inline void pyr_down(const CView<tFormat> &pSrc,const CMView<tFormat> &pDst)
{
    cv::pyrDown(mat_wrap(pSrc),mat_wrap(pDst),cv::Size(pDst.Width(),pDst.Height()));
}

enum
{
    FLIP_HORZ=1,
    FLIP_VERT=0,
    FLIP_BOTH=-1
};

template<typename tView>
inline void flip(const tView &pSrc,const tView &pDst,int pType)
{
    cvFlip(mat_wrap(pSrc),mat_wrap(pDst),pType);
}

void dist_transform(const CView_gray8 &pSrc,const CMView_gray32f &pDst,EDistType pDistType=DIST_L2,int pMaskSize=3,
                    const CMView_xy32s &pOri=CMView_xy32s());

inline void dist_transform(const img::CView_gray8 &pSrc,const img::CMView_gray32f &pDst)
{
    cv::distanceTransform(mat_wrap(pSrc),mat_wrap(pDst),CV_DIST_L2,3);
}

inline void transform_image(const img::CView_gray8 &pSrc,const img::CMView_gray8 &pDst, float pYDif, float pZRot, float pScale)
{
    const unsigned char *lSrc=pSrc.RawData();
    unsigned char *lDst=pDst.RawData();
    int lW=pSrc.Width();
    int lH=pSrc.Height();
    float lSin=sin(pZRot)/pScale;
    float lCos=cos(pZRot)/pScale;
    float lCX = (1-lCos)*(lW/2) - lSin*(-lH/2) + lSin*pYDif;
    float lCY = lSin*(-lW/2) + (1-lCos)*(lH/2) - lCos*pYDif;

    for (int y=0;y<lH;y++)
    {
        float lPX = lCX - lSin*y;
        float lPY = lCY + lCos*y;
        for (int x=0;x<lW;x++)
        {
            int lP = (int)lPY*lW + (int)lPX;
            if (lPX>=0 && lPX<(lW-1) && lPY>=0 && lPY<(lH-1))
            {
                float lDX = lPX - (int)lPX;
                float lDY = lPY - (int)lPY;
                lDst[y*lW+x] = (unsigned char) ((1.0f-lDY) * ((1.0f-lDX)*lSrc[lP] + lDX*lSrc[lP+1]) +
                        lDY * ((1.0f-lDX)*lSrc[lP+lW] + lDX*lSrc[lP+lW+1]));
            }
            else
            {
                lDst[y*lW+x]=0;
            }
            lPX+=lCos;
            lPY+=lSin;
        }
    }
}

inline void transform_image(const img::CView_rgb8 &pSrc,const img::CMView_rgb8 &pDst, float pYDif, float pZRot, float pScale)
{
    const unsigned char *lSrc=pSrc.RawData();
    unsigned char *lDst=pDst.RawData();
    int lW=pSrc.Width();
    int lH=pSrc.Height();
    float lSin=sin(pZRot)/pScale;
    float lCos=cos(pZRot)/pScale;
    float lCX = (1-lCos)*(lW/2) - lSin*(-lH/2) + lSin*pYDif;
    float lCY = lSin*(-lW/2) + (1-lCos)*(lH/2) - lCos*pYDif;

    for (int y=0;y<lH;y++)
    {
        float lPX = lCX - lSin*y;
        float lPY = lCY + lCos*y;
        for (int x=0;x<lW;x++)
        {
            int lP = 3*((int)lPY*lW + (int)lPX);
            int lD = 3*(y*lW+x);
            if (lPX>=0 && lPX<(lW-1) && lPY>=0 && lPY<(lH-1))
            {
                float lDX = lPX - (int)lPX;
                float lDY = lPY - (int)lPY;
                lDst[lD] = (unsigned char) ((1.0f-lDY) * ((1.0f-lDX)*lSrc[lP] + lDX*lSrc[lP+3]) +
                        lDY * ((1.0f-lDX)*lSrc[lP+3*lW] + lDX*lSrc[lP+3*lW+3]));
                lDst[lD+1] = (unsigned char) ((1.0f-lDY) * ((1.0f-lDX)*lSrc[lP+1] + lDX*lSrc[lP+3+1]) +
                        lDY * ((1.0f-lDX)*lSrc[lP+3*lW+1] + lDX*lSrc[lP+3*lW+3+1]));
                lDst[lD+2] = (unsigned char) ((1.0f-lDY) * ((1.0f-lDX)*lSrc[lP+2] + lDX*lSrc[lP+3+2]) +
                        lDY * ((1.0f-lDX)*lSrc[lP+3*lW+2] + lDX*lSrc[lP+3*lW+3+2]));
            }
            else
            {
                lDst[lD]=0;
                lDst[lD+1]=0;
                lDst[lD+2]=0;
            }
            lPX+=lCos;
            lPY+=lSin;
        }
    }
}

template<typename tF1,typename tF2>
inline void cart_to_polar(const img::CView<tF1> &pX,const img::CView<tF1> &pY,const img::CMView<tF2> &pMag,const img::CMView<tF2> &pOri,int pAngleInDegrees=false)
{
    cv::cartToPolar(mat_wrap(pX),mat_wrap(pY),mat_wrap(pMag),mat_wrap(pOri),pAngleInDegrees);
}

template<typename tF1,typename tF2>
inline void cart_to_phase(const img::CView<tF1> &pX,const img::CView<tF1> &pY,const img::CMView<tF2> &pOri,int pAngleInDegrees=false)
{
    cv::phase(mat_wrap(pX),mat_wrap(pY),mat_wrap(pOri),pAngleInDegrees);
}

inline void disparity_to_depth(const img::CMView_gray32f &pImg,float pFocal,float pBaseline)
{
    float lFactor=pFocal*pBaseline;

    for(int y=0;y<pImg.Height();y++)
    {
        for(int x=0;x<pImg.Width();x++)
        {
            pImg(x,y)=lFactor/pImg(x,y);
        }
    }
}

//@}
    
/*namespace image*/ } /*namespace buola*/ }

#endif
